/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  						       quicksand.cpp

	$Header: /game/quicksand.cpp $

	$NoKeywords: $

 ************************************************************************/


// kill the 255+ character identifier warning.  Templates tend to exceed that easily.
#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "quicksand.h"

#include "battlefield.h"
#include "battlefield_window.h"
#include "bitmap_cursor.h"
#include "bitmap_group_cache.h"
#include "can_cast.h"
#include "combat_creature.h"
#include "combat_footprint.h"
#include "combat_object_type.h"
#include "combat_reader.h"
#include "combat_spell_registration.h"
#include "counted_animation.h"
#include "counted_idle_processor.h"
#include "direction.h"
#include "hero.h"
#include "external_string.h"
#include "mouse_window.h"
#include "options.h"

// --------------------------------------------------------------
// quicksand object
// --------------------------------------------------------------
namespace
{

// --------------------------------------------------------------
// quicksand animator
// --------------------------------------------------------------
	class t_quicksand_creator : public t_counted_idle_processor, public t_counted_animation
	{
	public:
		explicit t_quicksand_creator( t_battlefield& battlefield, t_quicksand& quicksand );

		virtual void on_idle();
	protected:
		t_quicksand& m_quicksand;
	};
};


static t_combat_actor_model_ptr get_quicksand_model( double scale )
{
	static t_combat_actor_model_cache_set k_models( "spells.quicksand" );

	return k_models.get( scale );
}

// --------------------------------------------------------------
// quicksand object
// --------------------------------------------------------------
t_quicksand::t_quicksand( t_battlefield& battlefield )
           : t_combat_actor( &battlefield, get_quicksand_model( battlefield.get_model_scale() ),
		                     k_combat_actor_action_cast_spell, k_direction_northeast ),
							 m_battlefield( battlefield )
{
}

// --------------------------------------------------------------
// quicksand object
// --------------------------------------------------------------
bool t_quicksand::is_quicksand() const
{
	return true;
}

// --------------------------------------------------------------
// quicksand object
// --------------------------------------------------------------
bool t_quicksand::is_underlay() const
{
	return true;
}

t_combat_object_type t_quicksand::get_object_type() const
{
	return k_combat_object_type_quicksand;
}

bool t_quicksand::read( t_combat_reader& reader )
{
	t_battlefield& battlefield = reader.get_battlefield();

	if (!t_combat_actor::read( reader ))
		return false;
	m_model_ptr = get_quicksand_model( battlefield.get_model_scale() );
	return true;
}

// --------------------------------------------------------------
// quicksand object
// --------------------------------------------------------------
void t_quicksand::on_placed()
{
	t_combat_footprint const& footprint = get_footprint();
	int                       footprint_size = footprint.get_size();
	t_map_point_2d            point = get_cell_position();
	t_map_point_2d            offset;
	t_map_point_2d            new_point;
	int const*				  ptr = footprint.get();

	for (offset.row = 0; offset.row < footprint_size; offset.row++)
	{
		offset.column = 0;
		for (offset.column = 0; offset.column < footprint_size; offset.column++)
		{
			if (!(*(ptr++)))
				continue;
			new_point = point + offset;
			m_battlefield.get_cell( new_point ).set_quicksand( true );
		}
	}
}

// --------------------------------------------------------------
// quicksand object
// --------------------------------------------------------------
void t_quicksand::on_removed()
{
}

// --------------------------------------------------------------
// quicksand animator
// --------------------------------------------------------------
t_quicksand_creator::t_quicksand_creator( t_battlefield& battlefield, t_quicksand& quicksand )
: t_counted_idle_processor( 100000 / (quicksand.get_frames_per_second() 
						              * get_combat_animation_speed())),
  t_counted_animation( battlefield ), m_quicksand( quicksand )
{
}

// --------------------------------------------------------------
// quicksand animator
// --------------------------------------------------------------
void t_quicksand_creator::on_idle()
{
	int frame = m_quicksand.get_current_frame_num();
	int frame_count = m_quicksand.get_frame_count();

	frame++;
	if (frame < frame_count)
	{
		m_battlefield.set_current_frame_num( m_quicksand, frame );
		return;
	}
	
	t_idle_ptr ref = this;

	animation_ended();
	m_quicksand.set_animation( 0 );
	m_battlefield.update_state();
}

static t_combat_spell_registration<t_quicksand_spell>
	   k_register_quicksand( k_spell_quicksand );
static t_bitmap_group_cache const k_bucket( "cursor.combat.quicksand" );

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
t_quicksand_spell::t_quicksand_spell( t_battlefield& battlefield, t_spell spell )
                 : t_combat_spell( battlefield, spell )
{
	t_bitmap_cursor cursor;

	m_quicksand = new t_quicksand( battlefield );
	m_dragging = false;
	cursor.create( k_bucket.get() );
	m_bucket_cursor = cursor.cursor;
}

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
bool t_quicksand_spell::begin_casting()
{
	return true;
}

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
bool t_quicksand_spell::can_cast( t_screen_point const& point ) const
{
	t_map_point_2d map_point;

	if (!get_map_point( point, map_point ))
		return false;
	return can_cast( map_point );
}

double t_quicksand_spell::get_cancel_weight( t_combat_creature const& target ) const
{
	return 0.0;
}
// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
bool t_quicksand_spell::can_cast( t_map_point_2d const& map_point ) const
{
	if (!m_battlefield.can_place( *m_quicksand, map_point ))
		return false;

	t_battlefield_cell const&      cell = m_battlefield.get_cell( map_point );

	if (!cell.is_quicksand())
		return true;

	t_combat_object_list const&    objects = cell.get_objects();
	t_combat_object_const_iterator index = objects.begin();
	t_combat_object_const_iterator end = objects.end();

	for (; index != end; index++)
	{
		if (index->get()->is_quicksand()
			&& index->get()->get_cell_position() == map_point)
			return false;
	}
	return true;
}


// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
void t_quicksand_spell::execute( t_combat_creature& caster )
{
	t_map_point_2d_list::iterator index = m_targets.begin();
	t_map_point_2d_list::iterator end = m_targets.end();
	t_quicksand_ptr				  quicksand;
	t_counted_ptr< t_quicksand_creator > animation;
	t_battlefield&				  battlefield = m_battlefield;
	t_combat_action_message		  message( *m_caster, get_action_text( false ) );

	pay_cost();
	play_sound();
	for (; index != end; index++)
	{
		quicksand = new t_quicksand( m_battlefield );
		battlefield.place_object( quicksand, (*index) << k_battlefield_subcell_shift );
		animation = new t_quicksand_creator( m_battlefield, *quicksand );
		animation->display_action_message( message );
		quicksand->set_animation( animation.get() );
	}
	battlefield.get_data_cache().clear_terrain_maps();

}

// --------------------------------------------------------------------
// Generate the list of casting actions the given caster could take
// for this spell
// --------------------------------------------------------------------
t_combat_ai_action_list t_quicksand_spell::generate_combat_ai_action_list(t_combat_ai &owner)
{
	// Not implemented
	return t_combat_ai_action_list();
}

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
bool t_quicksand_spell::is_covered( t_map_point_2d const& point ) const
{
	if (m_battlefield.get_cell( point ).is_quicksand())
		return true;
	
	t_map_point_2d_list::const_iterator index = m_targets.begin();
	t_map_point_2d_list::const_iterator end = m_targets.end();
	t_map_point_2d						delta;
	int								    footprint_size = m_quicksand->get_footprint_size();

	for (; index != end; index++)
	{
		delta = point - *index;
		if (delta.column >= 0 && delta.row >= 0
			&& delta.column < footprint_size
			&& delta.row < footprint_size)
			return true;
	}
	return false;
}

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
int t_quicksand_spell::count_squares( t_map_point_2d const& point ) const
{
	t_map_point_2d offset;
	int			   footprint_size = m_quicksand->get_footprint_size();
	int			   result = 0;

	for (offset.row = 0; offset.row < footprint_size; offset.row++)
	{
		for (offset.column = 0; offset.column < footprint_size; offset.column++)
		{
			if (!is_covered( point + offset ))
				result++;
		}
	}
	return result;
}

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
void t_quicksand_spell::eliminate_extra_points() 
{
	t_map_point_2d_list::iterator index;
	t_map_point_2d_list::iterator next = m_targets.begin();
	t_map_point_2d                point;

	while (next != m_targets.end())
	{
		index = next;
		next++;
		point = *index;
		m_targets.erase( index );
		if (count_squares( point ) > 0)
			m_targets.insert( next, point );
	}
}

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
static t_external_string const k_text_cast_here( "cast_here.spells" );

t_mouse_window* t_quicksand_spell::mouse_move( t_screen_point const& point,
									           std::string& help_text )
{
	if (!can_cast( point ))
		return m_battlefield.get_blocked_cursor();
	if (!m_dragging)
	{
		help_text = replace_spell_keywords( k_text_cast_here, *m_caster, m_spell, 0 );
		return m_battlefield.get_spell_cursor();
	}

	t_map_point_2d start = m_targets.back();
	t_screen_point screen_delta;
	t_map_point_2d map_point;
	int			   limit;

	// replace text
	help_text = replace_spell_keywords( k_text_cast_here, *m_caster, m_spell, 0 );
	// don't place a new point unless the cursor has moved a reasonable amount
	screen_delta = point - m_last_point;
	limit = 32 * m_battlefield.get_model_scale();
	if (abs( screen_delta.x ) < limit
		&& abs( screen_delta.y ) < limit / 2
		&& !(abs( screen_delta.x ) >= limit / 2 && abs( screen_delta.y ) >= limit / 4))
		return m_bucket_cursor;

	// don't place a new point if this is already quicksand
	get_map_point( point, map_point );
	if (std::find( m_targets.begin(), m_targets.end(), map_point ) != m_targets.end())
		return m_bucket_cursor;

	// interpolate points between here and last point
	t_map_point_2d delta = map_point - start;
	t_map_point_2d new_point;
	t_map_point_2d sum(0,0);
	int			   divisor;
	int			   i;
	int			   count;
	int			   max_points;

	divisor = abs( delta.row );
	if (divisor < abs( delta.column ))
		divisor = abs( delta.column );

	max_points = m_caster->get_spell_power( m_spell );
	for (i = 0; i < divisor; i++)
	{
		sum += delta;
		new_point = delta / divisor + start;
		if (!can_cast( new_point ))
			continue;
		if (std::find( m_targets.begin(), m_targets.end(), new_point ) != m_targets.end())
			continue;
		count = count_squares( new_point );
		if (count == 0)
			continue;
		m_targets.push_back( new_point );
		m_covered += count;
		eliminate_extra_points();
		if (m_covered >= max_points)
		{
			m_battlefield.begin_spell( m_targets.back() );
			help_text = "";
			return m_battlefield.get_normal_cursor();
		}
	}
	m_last_point = point;
	return m_bucket_cursor;
}

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
bool t_quicksand_spell::left_click( t_screen_point const& point )
{
	if (m_dragging)
		return false;
	if (!can_cast( point ))
		return false;

	t_map_point_2d map_point;

	get_map_point( point, map_point );
	m_covered = count_squares( map_point );
	m_targets.push_back( map_point );
	m_last_point = point;
	m_battlefield.get_window()->set_cursor( m_bucket_cursor );
	m_dragging = true;
	return true;
}

// --------------------------------------------------------------
// quicksand spell
// --------------------------------------------------------------
bool t_quicksand_spell::get_map_point( t_screen_point const& screen_point, 
		                               t_map_point_2d& map_point ) const
{
	if (!m_battlefield.cell_hit_test( screen_point, map_point ))
		return false;

	int footprint_size = m_quicksand->get_footprint_size();

	map_point.row -= (footprint_size - 1) / 2;
	map_point.column -= (footprint_size - 1) / 2;

	if ((footprint_size & 1) == 0)
	{
		t_map_point_3d center = m_battlefield.get_cell_center( map_point );
		t_screen_point center_screen_point;

		center_screen_point = m_battlefield.subcell_to_screen_point( center );
		if (center_screen_point.y < screen_point.y)
		{
			map_point.column--;
			map_point.row--;
		}
	}
	return m_battlefield.is_valid( map_point );
}
